/*
 Copyright 2007 Razvan Taranu

 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
 You may obtain a copy of the License at

 http://www.apache.org/licenses/LICENSE-2.0

 Unless required by applicable law or agreed to in writing, software
 distributed under the License is distributed on an "AS IS" BASIS,
 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 See the License for the specific language governing permissions and
 limitations under the License.
 */
package generators;

import java.io.File;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.commons.configuration.Configuration;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import com.thoughtworks.qdox.JavaDocBuilder;
import com.thoughtworks.qdox.model.JavaClass;
import com.thoughtworks.qdox.model.JavaSource;
import com.thoughtworks.qdox.model.Type;

public class JavaHelper {
    private static final Log log = LogFactory.getLog(JavaHelper.class);

    private static Set<JavaSource> remoteServiceSources;
    private static Set<String> remoteServiceClasses;

    private static JavaDocBuilder jdb;

    private static String servicesPackage;

    private static String jsonizersPackage;
    private static String proxiesPackage;

    private static File jsonizersDirectory;
    private static File proxiesDirectory;

    public static void configure(JavaDocBuilder jdb, Configuration config) {
        JavaHelper.jdb = jdb;

        Beans.beansPackage = config.getString("java.beans.package");
        servicesPackage = config.getString("java.services.package");

        jsonizersPackage = config.getString("java.jsonizers.package");
        proxiesPackage = config.getString("java.proxies.package");

        String outputPath = config.getString("java.output.path");

        // create the output directories for the jsonizer and proxy classes
        String jsonziersPath = jsonizersPackage.replaceAll("\\.", "/");
        jsonizersDirectory = new File(outputPath + "/" + jsonziersPath);
        if (jsonizersDirectory.exists() && false == jsonizersDirectory.isDirectory()) {
            throw new RuntimeException(jsonizersDirectory + " exists and is not a directory");
        }
        jsonizersDirectory.mkdirs();

        String proxiesPath = proxiesPackage.replaceAll("\\.", "/");
        proxiesDirectory = new File(outputPath + "/" + proxiesPath);
        if (proxiesDirectory.exists() && false == proxiesDirectory.isDirectory()) {
            throw new RuntimeException(proxiesDirectory + " exists and is not a directory");
        }
        proxiesDirectory.mkdirs();

        cacheBeansServices();
    }

    public static Set<String> getRemoteServiceClassNames() {
        return Collections.unmodifiableSet(remoteServiceClasses);
    }

    public static Set<JavaSource> getRemoteServiceSources() {
        return Collections.unmodifiableSet(remoteServiceSources);
    }

    /**
     * Create a list of bean classes and service interfaces that will be processed.
     */
    private static void cacheBeansServices() {
        remoteServiceSources = new HashSet<JavaSource>();
        remoteServiceClasses = new HashSet<String>();

        log.info("generating cache of Serializable and RemoteService classes");

        for (JavaSource javaSource : jdb.getSources()) {
            log.debug("analyzing " + javaSource.getURL());

            if (Beans.checkSerializable(javaSource)) {
                Beans.createBeanCache(javaSource);
            }

            if (checkRemoteService(javaSource)) {
                String className = javaSource.getClasses()[0].getFullyQualifiedName();

                log.info(className + " is a remote service");
                remoteServiceSources.add(javaSource);
                remoteServiceClasses.add(className);
            }
        }
    }

    private static boolean checkRemoteService(JavaSource source) {
        String sourcePackage = source.getPackage().getName();

        if (false == servicesPackage.equals(sourcePackage)) {
            log.debug("source package [" + sourcePackage + "] != java.services.package [" + servicesPackage + "]");
            return false;
        }

        JavaClass[] classes = source.getClasses();

        if (classes.length != 1) {
            log.debug(source.getURL() + " has inner classes");
            return false;
        }

        JavaClass javaClass = classes[0];

        boolean isRemoteService = false;
        for (Type implemented : javaClass.getImplements()) {
            if (implemented.getValue().equals("com.google.gwt.user.client.rpc.RemoteService")) {
                isRemoteService = true;
            }
        }
        if (false == isRemoteService) {
            log.debug(javaClass.getFullyQualifiedName() + " does not implement RemoteService");
            return false;
        }

        if (javaClass.getMethods().length == 0) {
            log.debug(javaClass.getFullyQualifiedName() + " does not have any methods");
            return false;
        }

        return true;
    }

    public static void writeJsonizer(String beanName, String content) {
        File outputFile = new File(jsonizersDirectory, beanName + "Jsonizer.java");

        log.info("writing jsonizer for bean " + beanName + " to " + outputFile);
        Utils.writeFile(content, outputFile);
    }

    public static void writeProxy(String serviceName, String content) {
        File outputFile = new File(proxiesDirectory, serviceName + "Proxy.java");

        log.info("writing proxy for service " + serviceName + " to " + outputFile);
        Utils.writeFile(content, outputFile);
    }

    public static String valueToJava(String value, Type type) {
        String template = "@@jsonizer@@Jsonizer.toJava@@type@@(@@value@@)";

        String className = type.getValue();
        boolean isArray = type.isArray();
        String genericClass = Beans.getGenericClassName(type);

        log.debug("toJava className: [" + className + "] isArray: [" + isArray + "] genericClass: [" + genericClass + "]");
        String jsonizer = jsonizer(template, className, isArray, genericClass);

        // jsonizer = ObjectJsonizer.toJsonString(@@value@@)

        if (Beans.isSerializable(className) || (List.class.getName().equals(className) && Beans.isSerializable(genericClass))
                || (Map.class.getName().equals(className) && Beans.isSerializable(genericClass))) {
            jsonizer = jsonizer.replace("@@value@@", "@@value@@, helper");
        }

        return jsonizer.replace("@@value@@", value);
    }

    public static String valueToJson(String value, Type type) {
        String template = "@@jsonizer@@Jsonizer.toJson@@type@@(@@value@@)";

        String className = type.getValue();
        boolean isArray = type.isArray();
        String genericClass = Beans.getGenericClassName(type);

        log.debug("toJson className: [" + className + "] isArray: [" + isArray + "] genericClass: [" + genericClass + "]");
        String jsonizer = jsonizer(template, className, isArray, genericClass);

        // jsonizer = ObjectJsonizer.toJsonString(@@value@@)

        if (Beans.isSerializable(className) || (List.class.getName().equals(className) && Beans.isSerializable(genericClass))
                || (Map.class.getName().equals(className) && Beans.isSerializable(genericClass))) {
            jsonizer = jsonizer.replace("@@value@@", "@@value@@, helper");
        }

        return jsonizer.replace("@@value@@", value);
    }

    private static final Map<String, String> objects = new HashMap<String, String>();
    static {
        objects.put("java.lang.Character", "Char");
        objects.put("java.lang.String", "String");
        objects.put("java.lang.Boolean", "Boolean");
        objects.put("java.lang.Short", "Short");
        objects.put("java.lang.Integer", "Integer");
        objects.put("java.lang.Long", "Long");
        objects.put("java.lang.Float", "Float");
        objects.put("java.lang.Double", "Double");
        objects.put("java.util.Date", "Date");
    }

    private static final Map<String, String> primitives = new HashMap<String, String>();
    static {
        primitives.put("char", "Char");
        primitives.put("boolean", "Boolean");
        primitives.put("short", "Short");
        primitives.put("int", "Integer");
        primitives.put("long", "Long");
        primitives.put("float", "Float");
        primitives.put("double", "Double");
    }

    public static String jsonizer(String template, String className, boolean isArray, String genericClassName) {
        String jsonizer, type;

        if (primitives.containsKey(className)) {
            jsonizer = (isArray) ? "PrimitiveArray" : "Primitive";
            type = primitives.get(className);

        } else if (objects.containsKey(className)) {
            jsonizer = (isArray) ? "ObjectArray" : "Object";
            type = objects.get(className);

        } else if (className.equals("java.util.List")) {
            if (objects.containsKey(genericClassName)) {
                jsonizer = "List";
                type = objects.get(genericClassName);

            } else {
                if (false == Beans.isSerializable(genericClassName)) {
                    throw new RuntimeException("unknown generic class: " + genericClassName);
                }
                jsonizer = getBeanName(genericClassName);
                type = "List";

            }

        } else if (className.equals("java.util.Map")) {
            if (objects.containsKey(genericClassName)) {
                jsonizer = "Map";
                type = objects.get(genericClassName);

            } else {
                if (false == Beans.isSerializable(genericClassName)) {
                    throw new RuntimeException("unknown typeArg: " + genericClassName);
                }
                jsonizer = getBeanName(genericClassName);
                type = "Map";

            }
        } else {
            if (false == Beans.isSerializable(className)) {
                throw new RuntimeException("unknown javaType: " + className);
            }
            jsonizer = getBeanName(className);
            type = (isArray) ? "Array" : "";
        }

        template = template.replace("@@jsonizer@@", jsonizer);
        template = template.replace("@@type@@", type);

        return template;
    }

    public static boolean isRemoteService(JavaSource source) {
        return remoteServiceSources.contains(source);
    }

    public static String getBeanName(String className) {
        return jdb.getClassByName(className).getName();
    }

    private static final Map<String, String> jsonizers;
    static {
        jsonizers = new HashMap<String, String>();
        jsonizers.put("ListJsonizer.java", "templates/java/jsonizer/ListJsonizer.tpl");
        jsonizers.put("MapJsonizer.java", "templates/java/jsonizer/MapJsonizer.tpl");

        jsonizers.put("ObjectJsonizer.java", "templates/java/jsonizer/ObjectJsonizer.tpl");
        jsonizers.put("ObjectArrayJsonizer.java", "templates/java/jsonizer/ObjectArrayJsonizer.tpl");

        jsonizers.put("PrimitiveJsonizer.java", "templates/java/jsonizer/PrimitiveJsonizer.tpl");
        jsonizers.put("PrimitiveArrayJsonizer.java", "templates/java/jsonizer/PrimitiveArrayJsonizer.tpl");

        jsonizers.put("JsonizerHelper.java", "templates/java/jsonizer/JsonizerHelper.tpl");
    }

    public static void copyJsonizers() {
        for (String outputFilename : jsonizers.keySet()) {
            String ressourcePath = jsonizers.get(outputFilename);

            File fileOut = new File(jsonizersDirectory, outputFilename);
            if (log.isDebugEnabled()) {
                log.debug("copying " + ressourcePath + " to " + fileOut);
            }

            String content = Utils.readRessource(ressourcePath);
            content = content.replaceAll("@@packageName@@", jsonizersPackage);

            Utils.writeFile(content, fileOut);
        }
    }

    private static final Map<String, String> proxies;
    static {
        proxies = new HashMap<String, String>();
        proxies.put("ProxyHelper.java", "templates/java/proxies/ProxyHelper.tpl");
    }

    public static void copyProxies() {
        for (String outputFilename : proxies.keySet()) {
            String ressourcePath = proxies.get(outputFilename);

            File fileOut = new File(proxiesDirectory, outputFilename);
            if (log.isDebugEnabled()) {
                log.debug("copying " + ressourcePath + " to " + fileOut);
            }

            String content = Utils.readRessource(ressourcePath);
            content = content.replaceAll("@@proxiesPackage@@", proxiesPackage);

            Utils.writeFile(content, fileOut);
        }
    }
}
